package jeconbond.economic.resources;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import jeconbond.automata.IOutcome;
import jeconbond.automata.behaviour.BehaviourExceprion;
import jeconbond.economic.market.strategy.ConcurrentBasedPerformPriceStrategy;
import jeconbond.economic.market.strategy.IPerformPriceStrategy;
import jeconbond.economic.market.strategy.MultiplyPerformPriceStrategy;
import jeconbond.economic.systems.market.IMarketResourceRedestributionSystem;
import jeconbond.economic.systems.market.IMoneyAccount;
import jeconbond.economic.systems.market.ISellProposition;

import laboratoryQ.BaseUtils;
import laboratoryQ.computation.IRandom;

public class ResourceUtils {
	private static final double EPS = 1e-5;

	public static boolean checkIfResourcesEnought_unsafe(
			ResourceStorrage inputRS,
			IOutcome out, 
			double eps
	) {
		for (ResourceBasket rb : out.getRequirements()) {
			if (rb.value > eps + inputRS.howMuchWasStored(rb.type)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * unsafe because the result is correct only if in out.getRequired() exist no repetitions.
	 */
	public static boolean checkIfResourcesEnought_unsafe(
			ResourceStorrage inputRS,
			IOutcome out
	) {
		return checkIfResourcesEnought_unsafe(inputRS, out, EPS);
	}
	
	public static boolean checkIfResourcesEnought(
			ResourceStorrage inputRS,
			IOutcome out,
			double eps
	) {
		ResourceStorrage rs = new ResourceStorrage();
		for (ResourceBasket rb : out.getRequirements()) {
			rs.storeResource(rb);
		}
		for (ResourceBasket rb : rs.listBaskets()) {
			if (rb.value - eps > inputRS.howMuchWasStored(rb.type)) {
				return false;
			}
		}
		return true;
	}
	
	public static boolean checkIfResourcesEnought(
			ResourceStorrage inputRS,
			IOutcome out
	) {
		return checkIfResourcesEnought(inputRS, out, EPS);
	}
	
	public static void processOut(
			ResourceStorrage inputRS,
			ResourceStorrage outputRS, 
			IOutcome out, 
			double eps
	) {
		processOutRequire(inputRS, out, eps);
		processOutProduce(outputRS, out);
	}

	public static void processOutProduce(
			ResourceStorrage outputRS,
			IOutcome out
	) {
		for (ResourceBasket rb : out.getProducedResources()) {
			outputRS.storeResource(rb.type, rb.value);
		}
	}

	private static void processOutRequire(
			ResourceStorrage inputRS,
			IOutcome out, 
			double eps
	) {
		for (ResourceBasket rb : out.getRequirements()) {
			double received = inputRS.receiveResource(rb.type, rb.value);
			if (received + eps < rb.value) {
				throw new BehaviourExceprion("Not enought " + rb.type.getName() + " call checkIfResourcesEnought befor processOut.");
			}
		}
	}
	
	public static void processOut(
			ResourceStorrage inputRS,
			ResourceStorrage outputRS, 
			IOutcome out
	) {
		processOut(inputRS, outputRS, out, EPS);
	}

	public static void sortSellPropositions(ISellProposition[] props) {
		int n = props.length;
		for (int i = 0; i < n; i++) {
			int m = i;
			for (int j = i + 1; j < n; j++) {
				if (props[j].getPrice() < props[m].getPrice()) {
					m = j;
				}
			}
			ISellProposition t = props[i]; props[i] = props[m]; props[m] = t;
		}
	}
	
	public static void processDeal(
			ISellProposition sellProposition, 
			double value,
			IMoneyAccount buyerAccount, 
			ResourceStorrage buyerStorrage,
			IMarketResourceRedestributionSystem mrrs
	) {
		IResourceType type = sellProposition.getType();
		double totalPrice = sellProposition.getPrice() * value;
		if (buyerAccount.getBalance() - totalPrice < -EPS) {
			throw new BehaviourExceprion("Not enought money.");
		}
		buyerAccount.changeBalance(-totalPrice);
		sellProposition.getSellerMoneyAccount().changeBalance(totalPrice);
		double valReceived = sellProposition.getSellerStorrage()
			.receiveResource(type, value);
		if (valReceived - value < -EPS) {
			throw new BehaviourExceprion("Not enought in storrage.");
		}
		buyerStorrage.storeResource(type, value);
		mrrs.onSellPerformed(sellProposition, value, buyerAccount);
	}
	
	public static IResourceType[] generateResources(String prefix, int count) {
		IResourceType[] result = new IResourceType[count];
		for (int i = 0; i < count; i++) {
			result[i] = new ResourceType(prefix + i);
		}
		return result;
	}

	public static ResourceStorrage copyStorrage(ResourceStorrage storrage) {
		ResourceStorrage result = new ResourceStorrage();
		for (ResourceBasket rb : storrage.listBaskets()) {
			result.storeResource(rb);
		}
		return result;
	}

	public static void substractStorrages(
			ResourceStorrage rsBig,
			ResourceStorrage rsSmall,
			boolean treatNegativeAsZero
	) {
		for (ResourceBasket rb : rsSmall.listBaskets()) {
			double val = rsBig.receiveResource(rb.type, rb.value);
			if (val < rb.value && !treatNegativeAsZero) {
				throw new RuntimeException("Can't substract.");
			}
		}
	}
	
	public static void divideStorrage(
			ResourceStorrage storr,
			double div
	) {
		for (ResourceBasket rb : storr.listBaskets()) {
			rb.value /= div;
		}
	}
	
	public static void addStorrages(
			ResourceStorrage rsResult,
			ResourceStorrage rs2add
	) {
		for (ResourceBasket rb : rs2add.listBaskets()) {
			rsResult.storeResource(rb);
		}
	}

	public static void propositions2storrage(
			Collection<ISellProposition> collection,
			ResourceStorrage storrage
	) {
		storrage.clear();
		for (ISellProposition p : collection) {
			IResourceType type = p.getType();
			storrage.storeResource(type, p.getSellerStorrage().howMuchWasStored(type));
		}
	}

	public static double expectedPrice(IResourceType type, IMarketResourceRedestributionSystem mrrs) {
		ISellProposition[] props = mrrs.getSellPropositionsList(type);
		double n = props.length * ( 2.0 / 3.0 );
		double cost = 0.0;
		double val = 0.0;
		for (int i = 0; i < n; i++) {
			double curval = props[i].getSellerStorrage().howMuchWasStored(type);
			cost += curval * props[i].getPrice();
			val += curval;
		}
		double price = cost / val;
		if (val == 0.0) {
			price = 1.0;
		}
		return price;
	}

	public static ResourceBasket[] randomBaskets(
			IResourceType[] resources, 
			IRandom random, 
			int minSize, 
			int maxSize
	) {
		int n = minSize + BaseUtils.randomInt(
				random,
				maxSize - minSize
		);
		ResourceBasket[] cost = new ResourceBasket[n];
		for (int i = 0; i < n; i++) {
			cost[i] = randomBasket(resources, random);
		}
		return cost;
	}
	
	public static ResourceBasket randomBasket(IResourceType[] resources, IRandom random) {
		return new ResourceBasket(
				getRandomResource(resources, random),
				random.get()
		);
	}

	private static IResourceType getRandomResource(IResourceType[] resources, IRandom random) {
		int pos = BaseUtils.randomInt(
				random, 
				resources.length
		);
		return resources[pos];
	}

	public static double avaluateMarketPrice(IResourceType rt, IMarketResourceRedestributionSystem mrrs) {
		ISellProposition[] propositions =
			mrrs.getSellPropositionsList(rt);
		ResourceUtils.sortSellPropositions(propositions);
		int n = propositions.length;
		double totalPrice = 0.0;
		double totalValue = 0.0;
		for (int i = 0; i < n; i++) {
			totalPrice += propositions[i].getPrice() *
				propositions[i].getSellerStorrage().howMuchWasStored(rt);
			totalValue += propositions[i].getSellerStorrage().howMuchWasStored(rt);
		}
		double result = totalPrice / totalValue;
		return result;
	}

	public static IPerformPriceStrategy newConcurGreedPriceStrat(
			IPerformPriceStrategy baseStrategy, 
			double agentGreedPercent,
			double concurrentPricePercent, 
			Map<IResourceType, double[]> concurrentPrices
	) {
		MultiplyPerformPriceStrategy greedStrat =
			new MultiplyPerformPriceStrategy(baseStrategy, agentGreedPercent);
		if (concurrentPricePercent <= 0.0) {
			return greedStrat;
		}
		ConcurrentBasedPerformPriceStrategy concStrat = new ConcurrentBasedPerformPriceStrategy(
				greedStrat, 
				concurrentPricePercent, 
				concurrentPrices
		);
		return concStrat;
	}
	
	public static double[][] depMatrixFromGroups(double value, int[] ... groups) {
		int n = 0;
		for (int[] g : groups) {
			n += g.length;
		}
		double[][] result = new double[n][n];
		for (double[] d : result) {
			Arrays.fill(d, 1.0);
		}
		for (int[] g : groups) {
			for (int p : g) {
				for (int q : g) {
					result[p][q] = value;
				}
			}
		}
		return result;
	}
}
